/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef MIO_MMAP_HEADER
#define MIO_MMAP_HEADER

// #include "mio/page.hpp"
/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef MIO_PAGE_HEADER
#  define MIO_PAGE_HEADER

#  ifdef _WIN32
#    include <windows.h>
#  else
#    include <unistd.h>
#  endif

namespace mio
{
/**
 * This is used by `basic_mmap` to determine whether to create a read-only or
 * a read-write memory mapping.
 */
enum class access_mode
{
  read,
  write
};

/**
 * Determines the operating system's page allocation granularity.
 *
 * On the first call to this function, it invokes the operating system specific
 * syscall to determine the page size, caches the value, and returns it. Any
 * subsequent call to this function serves the cached value, so no further
 * syscalls are made.
 */
inline size_t page_size()
{
  static const size_t page_size = []
  {
#  ifdef _WIN32
    SYSTEM_INFO SystemInfo;
    GetSystemInfo(&SystemInfo);
    return SystemInfo.dwAllocationGranularity;
#  else
    return sysconf(_SC_PAGE_SIZE);
#  endif
  }();
  return page_size;
}

/**
 * Alligns `offset` to the operating's system page size such that it subtracts
 * the difference until the nearest page boundary before `offset`, or does
 * nothing if `offset` is already page aligned.
 */
inline size_t make_offset_page_aligned(size_t offset) noexcept
{
  const size_t page_size_ = page_size();
  // Use integer division to round down to the nearest page alignment.
  return offset / page_size_ * page_size_;
}

}  // namespace mio

#endif  // MIO_PAGE_HEADER

#include <cstdint>
#include <iterator>
#include <string>
#include <system_error>

#ifdef _WIN32
#  ifndef WIN32_LEAN_AND_MEAN
#    define WIN32_LEAN_AND_MEAN
#  endif  // WIN32_LEAN_AND_MEAN
#  include <windows.h>
#else  // ifdef _WIN32
#  define INVALID_HANDLE_VALUE -1
#endif  // ifdef _WIN32

namespace mio
{
// This value may be provided as the `length` parameter to the constructor or
// `map`, in which case a memory mapping of the entire file is created.
enum
{
  map_entire_file = 0
};

#ifdef _WIN32
using file_handle_type = HANDLE;
#else
using file_handle_type = int;
#endif

// This value represents an invalid file handle type. This can be used to
// determine whether `basic_mmap::file_handle` is valid, for example.
const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE;

template<access_mode AccessMode, typename ByteT>
struct basic_mmap
{
  using value_type = ByteT;
  using size_type = size_t;
  using reference = value_type&;
  using const_reference = const value_type&;
  using pointer = value_type*;
  using const_pointer = const value_type*;
  using difference_type = std::ptrdiff_t;
  using iterator = pointer;
  using const_iterator = const_pointer;
  using reverse_iterator = std::reverse_iterator<iterator>;
  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
  using iterator_category = std::random_access_iterator_tag;
  using handle_type = file_handle_type;

  static_assert(sizeof(ByteT) == sizeof(char),
                "ByteT must be the same size as char.");

private:
  // Points to the first requested byte, and not to the actual start of the
  // mapping.
  pointer data_ = nullptr;

  // Length--in bytes--requested by user (which may not be the length of the
  // full mapping) and the length of the full mapping.
  size_type length_ = 0;
  size_type mapped_length_ = 0;

  // Letting user map a file using both an existing file handle and a path
  // introcudes some complexity (see `is_handle_internal_`).
  // On POSIX, we only need a file handle to create a mapping, while on
  // Windows systems the file handle is necessary to retrieve a file mapping
  // handle, but any subsequent operations on the mapped region must be done
  // through the latter.
  handle_type file_handle_ = INVALID_HANDLE_VALUE;
#ifdef _WIN32
  handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE;
#endif

  // Letting user map a file using both an existing file handle and a path
  // introcudes some complexity in that we must not close the file handle if
  // user provided it, but we must close it if we obtained it using the
  // provided path. For this reason, this flag is used to determine when to
  // close `file_handle_`.
  bool is_handle_internal_;

public:
  /**
   * The default constructed mmap object is in a non-mapped state, that is,
   * any operation that attempts to access nonexistent underlying data will
   * result in undefined behaviour/segmentation faults.
   */
  basic_mmap() = default;

#ifdef __cpp_exceptions
  /**
   * The same as invoking the `map` function, except any error that may occur
   * while establishing the mapping is wrapped in a `std::system_error` and is
   * thrown.
   */
  template<typename String>
  basic_mmap(const String& path,
             const size_type offset = 0,
             const size_type length = map_entire_file)
  {
    std::error_code error;
    map(path, offset, length, error);
    if (error) {
      throw std::system_error(error);
    }
  }

  /**
   * The same as invoking the `map` function, except any error that may occur
   * while establishing the mapping is wrapped in a `std::system_error` and is
   * thrown.
   */
  basic_mmap(const handle_type handle,
             const size_type offset = 0,
             const size_type length = map_entire_file)
  {
    std::error_code error;
    map(handle, offset, length, error);
    if (error) {
      throw std::system_error(error);
    }
  }
#endif  // __cpp_exceptions

  /**
   * `basic_mmap` has single-ownership semantics, so transferring ownership
   * may only be accomplished by moving the object.
   */
  basic_mmap(const basic_mmap&) = delete;
  basic_mmap(basic_mmap&&);
  basic_mmap& operator=(const basic_mmap&) = delete;
  basic_mmap& operator=(basic_mmap&&);

  /**
   * If this is a read-write mapping, the destructor invokes sync. Regardless
   * of the access mode, unmap is invoked as a final step.
   */
  ~basic_mmap();

  /**
   * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On
   * Windows, however, a mapped region of a file gets its own handle, which is
   * returned by 'mapping_handle'.
   */
  handle_type file_handle() const noexcept
  {
    return file_handle_;
  }
  handle_type mapping_handle() const noexcept;

  /** Returns whether a valid memory mapping has been created. */
  bool is_open() const noexcept
  {
    return file_handle_ != invalid_handle;
  }

  /**
   * Returns true if no mapping was established, that is, conceptually the
   * same as though the length that was mapped was 0. This function is
   * provided so that this class has Container semantics.
   */
  bool empty() const noexcept
  {
    return length() == 0;
  }

  /** Returns true if a mapping was established. */
  bool is_mapped() const noexcept;

  /**
   * `size` and `length` both return the logical length, i.e. the number of
   * bytes user requested to be mapped, while `mapped_length` returns the actual
   * number of bytes that were mapped which is a multiple of the underlying
   * operating system's page allocation granularity.
   */
  size_type size() const noexcept
  {
    return length();
  }
  size_type length() const noexcept
  {
    return length_;
  }
  size_type mapped_length() const noexcept
  {
    return mapped_length_;
  }

  /** Returns the offset relative to the start of the mapping. */
  size_type mapping_offset() const noexcept
  {
    return mapped_length_ - length_;
  }

  /**
   * Returns a pointer to the first requested byte, or `nullptr` if no memory
   * mapping exists.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  pointer data() noexcept
  {
    return data_;
  }
  const_pointer data() const noexcept
  {
    return data_;
  }

  /**
   * Returns an iterator to the first requested byte, if a valid memory mapping
   * exists, otherwise this function call is undefined behaviour.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  iterator begin() noexcept
  {
    return data();
  }
  const_iterator begin() const noexcept
  {
    return data();
  }
  const_iterator cbegin() const noexcept
  {
    return data();
  }

  /**
   * Returns an iterator one past the last requested byte, if a valid memory
   * mapping exists, otherwise this function call is undefined behaviour.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  iterator end() noexcept
  {
    return data() + length();
  }
  const_iterator end() const noexcept
  {
    return data() + length();
  }
  const_iterator cend() const noexcept
  {
    return data() + length();
  }

  /**
   * Returns a reverse iterator to the last memory mapped byte, if a valid
   * memory mapping exists, otherwise this function call is undefined
   * behaviour.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  reverse_iterator rbegin() noexcept
  {
    return reverse_iterator(end());
  }
  const_reverse_iterator rbegin() const noexcept
  {
    return const_reverse_iterator(end());
  }
  const_reverse_iterator crbegin() const noexcept
  {
    return const_reverse_iterator(end());
  }

  /**
   * Returns a reverse iterator past the first mapped byte, if a valid memory
   * mapping exists, otherwise this function call is undefined behaviour.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  reverse_iterator rend() noexcept
  {
    return reverse_iterator(begin());
  }
  const_reverse_iterator rend() const noexcept
  {
    return const_reverse_iterator(begin());
  }
  const_reverse_iterator crend() const noexcept
  {
    return const_reverse_iterator(begin());
  }

  /**
   * Returns a reference to the `i`th byte from the first requested byte (as
   * returned by `data`). If this is invoked when no valid memory mapping has
   * been created prior to this call, undefined behaviour ensues.
   */
  reference operator[](const size_type i) noexcept
  {
    return data_[i];
  }
  const_reference operator[](const size_type i) const noexcept
  {
    return data_[i];
  }

  /**
   * Establishes a memory mapping with AccessMode. If the mapping is
   * unsuccesful, the reason is reported via `error` and the object remains in a
   * state as if this function hadn't been called.
   *
   * `path`, which must be a path to an existing file, is used to retrieve a
   * file handle (which is closed when the object destructs or `unmap` is
   * called), which is then used to memory map the requested region. Upon
   * failure, `error` is set to indicate the reason and the object remains in an
   * unmapped state.
   *
   * `offset` is the number of bytes, relative to the start of the file, where
   * the mapping should begin. When specifying it, there is no need to worry
   * about providing a value that is aligned with the operating system's page
   * allocation granularity. This is adjusted by the implementation such that
   * the first requested byte (as returned by `data` or `begin`), so long as
   * `offset` is valid, will be at `offset` from the start of the file.
   *
   * `length` is the number of bytes to map. It may be `map_entire_file`, in
   * which case a mapping of the entire file is created.
   */
  template<typename String>
  void map(const String& path,
           const size_type offset,
           const size_type length,
           std::error_code& error);

  /**
   * Establishes a memory mapping with AccessMode. If the mapping is
   * unsuccesful, the reason is reported via `error` and the object remains in a
   * state as if this function hadn't been called.
   *
   * `path`, which must be a path to an existing file, is used to retrieve a
   * file handle (which is closed when the object destructs or `unmap` is
   * called), which is then used to memory map the requested region. Upon
   * failure, `error` is set to indicate the reason and the object remains in an
   * unmapped state.
   *
   * The entire file is mapped.
   */
  template<typename String>
  void map(const String& path, std::error_code& error)
  {
    map(path, 0, map_entire_file, error);
  }

  /**
   * Establishes a memory mapping with AccessMode. If the mapping is
   * unsuccesful, the reason is reported via `error` and the object remains in
   * a state as if this function hadn't been called.
   *
   * `handle`, which must be a valid file handle, which is used to memory map
   * the requested region. Upon failure, `error` is set to indicate the reason
   * and the object remains in an unmapped state.
   *
   * `offset` is the number of bytes, relative to the start of the file, where
   * the mapping should begin. When specifying it, there is no need to worry
   * about providing a value that is aligned with the operating system's page
   * allocation granularity. This is adjusted by the implementation such that
   * the first requested byte (as returned by `data` or `begin`), so long as
   * `offset` is valid, will be at `offset` from the start of the file.
   *
   * `length` is the number of bytes to map. It may be `map_entire_file`, in
   * which case a mapping of the entire file is created.
   */
  void map(const handle_type handle,
           const size_type offset,
           const size_type length,
           std::error_code& error);

  /**
   * Establishes a memory mapping with AccessMode. If the mapping is
   * unsuccesful, the reason is reported via `error` and the object remains in
   * a state as if this function hadn't been called.
   *
   * `handle`, which must be a valid file handle, which is used to memory map
   * the requested region. Upon failure, `error` is set to indicate the reason
   * and the object remains in an unmapped state.
   *
   * The entire file is mapped.
   */
  void map(const handle_type handle, std::error_code& error)
  {
    map(handle, 0, map_entire_file, error);
  }

  /**
   * If a valid memory mapping has been created prior to this call, this call
   * instructs the kernel to unmap the memory region and disassociate this
   * object from the file.
   *
   * The file handle associated with the file that is mapped is only closed if
   * the mapping was created using a file path. If, on the other hand, an
   * existing file handle was used to create the mapping, the file handle is not
   * closed.
   */
  void unmap();

  void swap(basic_mmap& other);

  /** Flushes the memory mapped page to disk. Errors are reported via `error`.
   */
  template<access_mode A = AccessMode>
  typename std::enable_if<A == access_mode::write, void>::type sync(
      std::error_code& error);

  /**
   * All operators compare the address of the first byte and size of the two
   * mapped regions.
   */

private:
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  pointer get_mapping_start() noexcept
  {
    return !data() ? nullptr : data() - mapping_offset();
  }

  const_pointer get_mapping_start() const noexcept
  {
    return !data() ? nullptr : data() - mapping_offset();
  }

  /**
   * The destructor syncs changes to disk if `AccessMode` is `write`, but not
   * if it's `read`, but since the destructor cannot be templated, we need to
   * do SFINAE in a dedicated function, where one syncs and the other is a noop.
   */
  template<access_mode A = AccessMode>
  typename std::enable_if<A == access_mode::write, void>::type
  conditional_sync();
  template<access_mode A = AccessMode>
  typename std::enable_if<A == access_mode::read, void>::type
  conditional_sync();
};

template<access_mode AccessMode, typename ByteT>
bool operator==(const basic_mmap<AccessMode, ByteT>& a,
                const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator!=(const basic_mmap<AccessMode, ByteT>& a,
                const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator<(const basic_mmap<AccessMode, ByteT>& a,
               const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator<=(const basic_mmap<AccessMode, ByteT>& a,
                const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator>(const basic_mmap<AccessMode, ByteT>& a,
               const basic_mmap<AccessMode, ByteT>& b);

template<access_mode AccessMode, typename ByteT>
bool operator>=(const basic_mmap<AccessMode, ByteT>& a,
                const basic_mmap<AccessMode, ByteT>& b);

/**
 * This is the basis for all read-only mmap objects and should be preferred over
 * directly using `basic_mmap`.
 */
template<typename ByteT>
using basic_mmap_source = basic_mmap<access_mode::read, ByteT>;

/**
 * This is the basis for all read-write mmap objects and should be preferred
 * over directly using `basic_mmap`.
 */
template<typename ByteT>
using basic_mmap_sink = basic_mmap<access_mode::write, ByteT>;

/**
 * These aliases cover the most common use cases, both representing a raw byte
 * stream (either with a char or an unsigned char/uint8_t).
 */
using mmap_source = basic_mmap_source<char>;
using ummap_source = basic_mmap_source<unsigned char>;

using mmap_sink = basic_mmap_sink<char>;
using ummap_sink = basic_mmap_sink<unsigned char>;

/**
 * Convenience factory method that constructs a mapping for any `basic_mmap` or
 * `basic_mmap` type.
 */
template<typename MMap, typename MappingToken>
MMap make_mmap(const MappingToken& token,
               int64_t offset,
               int64_t length,
               std::error_code& error)
{
  MMap mmap;
  mmap.map(token, offset, length, error);
  return mmap;
}

/**
 * Convenience factory method.
 *
 * MappingToken may be a String (`std::string`, `std::string_view`, `const
 * char*`, `std::filesystem::path`, `std::vector<char>`, or similar), or a
 * `mmap_source::handle_type`.
 */
template<typename MappingToken>
mmap_source make_mmap_source(const MappingToken& token,
                             mmap_source::size_type offset,
                             mmap_source::size_type length,
                             std::error_code& error)
{
  return make_mmap<mmap_source>(token, offset, length, error);
}

template<typename MappingToken>
mmap_source make_mmap_source(const MappingToken& token, std::error_code& error)
{
  return make_mmap_source(token, 0, map_entire_file, error);
}

/**
 * Convenience factory method.
 *
 * MappingToken may be a String (`std::string`, `std::string_view`, `const
 * char*`, `std::filesystem::path`, `std::vector<char>`, or similar), or a
 * `mmap_sink::handle_type`.
 */
template<typename MappingToken>
mmap_sink make_mmap_sink(const MappingToken& token,
                         mmap_sink::size_type offset,
                         mmap_sink::size_type length,
                         std::error_code& error)
{
  return make_mmap<mmap_sink>(token, offset, length, error);
}

template<typename MappingToken>
mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error)
{
  return make_mmap_sink(token, 0, map_entire_file, error);
}

}  // namespace mio

// #include "detail/mmap.ipp"
/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef MIO_BASIC_MMAP_IMPL
#  define MIO_BASIC_MMAP_IMPL

// #include "mio/mmap.hpp"

// #include "mio/page.hpp"

// #include "mio/detail/string_util.hpp"
/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#  ifndef MIO_STRING_UTIL_HEADER
#    define MIO_STRING_UTIL_HEADER

#    include <type_traits>

namespace mio
{
namespace detail
{
template<typename S,
         typename C = typename std::decay<S>::type,
         typename = decltype(std::declval<C>().data()),
         typename = typename std::enable_if<
             std::is_same<typename C::value_type, char>::value
#    ifdef _WIN32
             || std::is_same<typename C::value_type, wchar_t>::value
#    endif
             >::type>
struct char_type_helper
{
  using type = typename C::value_type;
};

template<class T>
struct char_type
{
  using type = typename char_type_helper<T>::type;
};

// TODO: can we avoid this brute force approach?
template<>
struct char_type<char*>
{
  using type = char;
};

template<>
struct char_type<const char*>
{
  using type = char;
};

template<size_t N>
struct char_type<char[N]>
{
  using type = char;
};

template<size_t N>
struct char_type<const char[N]>
{
  using type = char;
};

#    ifdef _WIN32
template<>
struct char_type<wchar_t*>
{
  using type = wchar_t;
};

template<>
struct char_type<const wchar_t*>
{
  using type = wchar_t;
};

template<size_t N>
struct char_type<wchar_t[N]>
{
  using type = wchar_t;
};

template<size_t N>
struct char_type<const wchar_t[N]>
{
  using type = wchar_t;
};
#    endif  // _WIN32

template<typename CharT, typename S>
struct is_c_str_helper
{
  static constexpr bool value = std::is_same<
      CharT*,
      // TODO: I'm so sorry for this... Can this be made cleaner?
      typename std::add_pointer<
          typename std::remove_cv<typename std::remove_pointer<
              typename std::decay<S>::type>::type>::type>::type>::value;
};

template<typename S>
struct is_c_str
{
  static constexpr bool value = is_c_str_helper<char, S>::value;
};

#    ifdef _WIN32
template<typename S>
struct is_c_wstr
{
  static constexpr bool value = is_c_str_helper<wchar_t, S>::value;
};
#    endif  // _WIN32

template<typename S>
struct is_c_str_or_c_wstr
{
  static constexpr bool value = is_c_str<S>::value
#    ifdef _WIN32
      || is_c_wstr<S>::value
#    endif
      ;
};

template<typename String,
         typename = decltype(std::declval<String>().data()),
         typename =
             typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type>
const typename char_type<String>::type* c_str(const String& path)
{
  return path.data();
}

template<typename String,
         typename = decltype(std::declval<String>().empty()),
         typename =
             typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type>
bool empty(const String& path)
{
  return path.empty();
}

template<
    typename String,
    typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type>
const typename char_type<String>::type* c_str(String path)
{
  return path;
}

template<
    typename String,
    typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type>
bool empty(String path)
{
  return !path || (*path == 0);
}

}  // namespace detail
}  // namespace mio

#  endif  // MIO_STRING_UTIL_HEADER

#  include <algorithm>

#  ifndef _WIN32
#    include <fcntl.h>
#    include <sys/mman.h>
#    include <sys/stat.h>
#    include <unistd.h>
#  endif

namespace mio
{
namespace detail
{
#  ifdef _WIN32
namespace win
{
/** Returns the 4 upper bytes of an 8-byte integer. */
inline DWORD int64_high(int64_t n) noexcept
{
  return n >> 32;
}

/** Returns the 4 lower bytes of an 8-byte integer. */
inline DWORD int64_low(int64_t n) noexcept
{
  return n & 0xffffffff;
}

std::wstring s_2_ws(const std::string& s)
{
  if (s.empty())
    return {};
  const auto s_length = static_cast<int>(s.length());
  auto buf = std::vector<wchar_t>(s_length);
  const auto wide_char_count = MultiByteToWideChar(
      CP_UTF8, 0, s.c_str(), s_length, buf.data(), s_length);
  return std::wstring(buf.data(), wide_char_count);
}

template<typename String,
         typename = typename std::enable_if<
             std::is_same<typename char_type<String>::type, char>::value>::type>
file_handle_type open_file_helper(const String& path, const access_mode mode)
{
  return ::CreateFileW(
      s_2_ws(path).c_str(),
      mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE,
      0,
      OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      0);
}

template<typename String>
typename std::enable_if<
    std::is_same<typename char_type<String>::type, wchar_t>::value,
    file_handle_type>::type
open_file_helper(const String& path, const access_mode mode)
{
  return ::CreateFileW(
      c_str(path),
      mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE,
      0,
      OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      0);
}

}  // namespace win
#  endif  // _WIN32

/**
 * Returns the last platform specific system error (errno on POSIX and
 * GetLastError on Win) as a `std::error_code`.
 */
inline std::error_code last_error() noexcept
{
  std::error_code error;
#  ifdef _WIN32
  error.assign(GetLastError(), std::system_category());
#  else
  error.assign(errno, std::system_category());
#  endif
  return error;
}

template<typename String>
file_handle_type open_file(const String& path,
                           const access_mode mode,
                           std::error_code& error)
{
  error.clear();
  if (detail::empty(path)) {
    error = std::make_error_code(std::errc::invalid_argument);
    return invalid_handle;
  }
#  ifdef _WIN32
  const auto handle = win::open_file_helper(path, mode);
#  else  // POSIX
  const auto handle =
      ::open(c_str(path), mode == access_mode::read ? O_RDONLY : O_RDWR);
#  endif
  if (handle == invalid_handle) {
    error = detail::last_error();
  }
  return handle;
}

inline size_t query_file_size(file_handle_type handle, std::error_code& error)
{
  error.clear();
#  ifdef _WIN32
  LARGE_INTEGER file_size;
  if (::GetFileSizeEx(handle, &file_size) == 0) {
    error = detail::last_error();
    return 0;
  }
  return static_cast<int64_t>(file_size.QuadPart);
#  else  // POSIX
  struct stat sbuf;
  if (::fstat(handle, &sbuf) == -1) {
    error = detail::last_error();
    return 0;
  }
  return sbuf.st_size;
#  endif
}

struct mmap_context
{
  char* data;
  int64_t length;
  int64_t mapped_length;
#  ifdef _WIN32
  file_handle_type file_mapping_handle;
#  endif
};

inline mmap_context memory_map(const file_handle_type file_handle,
                               const int64_t offset,
                               const int64_t length,
                               const access_mode mode,
                               std::error_code& error)
{
  const int64_t aligned_offset = make_offset_page_aligned(offset);
  const int64_t length_to_map = offset - aligned_offset + length;
#  ifdef _WIN32
  const int64_t max_file_size = offset + length;
  const auto file_mapping_handle = ::CreateFileMapping(
      file_handle,
      0,
      mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE,
      win::int64_high(max_file_size),
      win::int64_low(max_file_size),
      0);
  if (file_mapping_handle == invalid_handle) {
    error = detail::last_error();
    return {};
  }
  char* mapping_start = static_cast<char*>(::MapViewOfFile(
      file_mapping_handle,
      mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE,
      win::int64_high(aligned_offset),
      win::int64_low(aligned_offset),
      length_to_map));
  if (mapping_start == nullptr) {
    // Close file handle if mapping it failed.
    ::CloseHandle(file_mapping_handle);
    error = detail::last_error();
    return {};
  }
#  else  // POSIX
  char* mapping_start = static_cast<char*>(
      ::mmap(0,  // Don't give hint as to where to map.
             length_to_map,
             mode == access_mode::read ? PROT_READ : PROT_WRITE,
             MAP_SHARED,
             file_handle,
             aligned_offset));
  if (mapping_start == MAP_FAILED) {
    error = detail::last_error();
    return {};
  }
#  endif
  mmap_context ctx;
  ctx.data = mapping_start + offset - aligned_offset;
  ctx.length = length;
  ctx.mapped_length = length_to_map;
#  ifdef _WIN32
  ctx.file_mapping_handle = file_mapping_handle;
#  endif
  return ctx;
}

}  // namespace detail

// -- basic_mmap --

template<access_mode AccessMode, typename ByteT>
basic_mmap<AccessMode, ByteT>::~basic_mmap()
{
  conditional_sync();
  unmap();
}

template<access_mode AccessMode, typename ByteT>
basic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap&& other)
    : data_(std::move(other.data_))
    , length_(std::move(other.length_))
    , mapped_length_(std::move(other.mapped_length_))
    , file_handle_(std::move(other.file_handle_))
#  ifdef _WIN32
    , file_mapping_handle_(std::move(other.file_mapping_handle_))
#  endif
    , is_handle_internal_(std::move(other.is_handle_internal_))
{
  other.data_ = nullptr;
  other.length_ = other.mapped_length_ = 0;
  other.file_handle_ = invalid_handle;
#  ifdef _WIN32
  other.file_mapping_handle_ = invalid_handle;
#  endif
}

template<access_mode AccessMode, typename ByteT>
basic_mmap<AccessMode, ByteT>& basic_mmap<AccessMode, ByteT>::operator=(
    basic_mmap&& other)
{
  if (this != &other) {
    // First the existing mapping needs to be removed.
    unmap();
    data_ = std::move(other.data_);
    length_ = std::move(other.length_);
    mapped_length_ = std::move(other.mapped_length_);
    file_handle_ = std::move(other.file_handle_);
#  ifdef _WIN32
    file_mapping_handle_ = std::move(other.file_mapping_handle_);
#  endif
    is_handle_internal_ = std::move(other.is_handle_internal_);

    // The moved from basic_mmap's fields need to be reset, because
    // otherwise other's destructor will unmap the same mapping that was
    // just moved into this.
    other.data_ = nullptr;
    other.length_ = other.mapped_length_ = 0;
    other.file_handle_ = invalid_handle;
#  ifdef _WIN32
    other.file_mapping_handle_ = invalid_handle;
#  endif
    other.is_handle_internal_ = false;
  }
  return *this;
}

template<access_mode AccessMode, typename ByteT>
typename basic_mmap<AccessMode, ByteT>::handle_type
basic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept
{
#  ifdef _WIN32
  return file_mapping_handle_;
#  else
  return file_handle_;
#  endif
}

template<access_mode AccessMode, typename ByteT>
template<typename String>
void basic_mmap<AccessMode, ByteT>::map(const String& path,
                                        const size_type offset,
                                        const size_type length,
                                        std::error_code& error)
{
  error.clear();
  if (detail::empty(path)) {
    error = std::make_error_code(std::errc::invalid_argument);
    return;
  }
  const auto handle = detail::open_file(path, AccessMode, error);
  if (error) {
    return;
  }

  map(handle, offset, length, error);
  // This MUST be after the call to map, as that sets this to true.
  if (!error) {
    is_handle_internal_ = true;
  }
}

template<access_mode AccessMode, typename ByteT>
void basic_mmap<AccessMode, ByteT>::map(const handle_type handle,
                                        const size_type offset,
                                        const size_type length,
                                        std::error_code& error)
{
  error.clear();
  if (handle == invalid_handle) {
    error = std::make_error_code(std::errc::bad_file_descriptor);
    return;
  }

  const auto file_size = detail::query_file_size(handle, error);
  if (error) {
    return;
  }

  if (offset + length > file_size) {
    error = std::make_error_code(std::errc::invalid_argument);
    return;
  }

  const auto ctx = detail::memory_map(
      handle,
      offset,
      length == map_entire_file ? (file_size - offset) : length,
      AccessMode,
      error);
  if (!error) {
    // We must unmap the previous mapping that may have existed prior to this
    // call. Note that this must only be invoked after a new mapping has been
    // created in order to provide the strong guarantee that, should the new
    // mapping fail, the `map` function leaves this instance in a state as
    // though the function had never been invoked.
    unmap();
    file_handle_ = handle;
    is_handle_internal_ = false;
    data_ = reinterpret_cast<pointer>(ctx.data);
    length_ = ctx.length;
    mapped_length_ = ctx.mapped_length;
#  ifdef _WIN32
    file_mapping_handle_ = ctx.file_mapping_handle;
#  endif
  }
}

template<access_mode AccessMode, typename ByteT>
template<access_mode A>
typename std::enable_if<A == access_mode::write, void>::type
basic_mmap<AccessMode, ByteT>::sync(std::error_code& error)
{
  error.clear();
  if (!is_open()) {
    error = std::make_error_code(std::errc::bad_file_descriptor);
    return;
  }

  if (data()) {
#  ifdef _WIN32
    if (::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0
        || ::FlushFileBuffers(file_handle_) == 0)
#  else  // POSIX
    if (::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0)
#  endif
    {
      error = detail::last_error();
      return;
    }
  }
#  ifdef _WIN32
  if (::FlushFileBuffers(file_handle_) == 0) {
    error = detail::last_error();
  }
#  endif
}

template<access_mode AccessMode, typename ByteT>
void basic_mmap<AccessMode, ByteT>::unmap()
{
  if (!is_open()) {
    return;
  }
  // TODO do we care about errors here?
#  ifdef _WIN32
  if (is_mapped()) {
    ::UnmapViewOfFile(get_mapping_start());
    ::CloseHandle(file_mapping_handle_);
  }
#  else  // POSIX
  if (data_) {
    ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_);
  }
#  endif

  // If `file_handle_` was obtained by our opening it (when map is called with
  // a path, rather than an existing file handle), we need to close it,
  // otherwise it must not be closed as it may still be used outside this
  // instance.
  if (is_handle_internal_) {
#  ifdef _WIN32
    ::CloseHandle(file_handle_);
#  else  // POSIX
    ::close(file_handle_);
#  endif
  }

  // Reset fields to their default values.
  data_ = nullptr;
  length_ = mapped_length_ = 0;
  file_handle_ = invalid_handle;
#  ifdef _WIN32
  file_mapping_handle_ = invalid_handle;
#  endif
}

template<access_mode AccessMode, typename ByteT>
bool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept
{
#  ifdef _WIN32
  return file_mapping_handle_ != invalid_handle;
#  else  // POSIX
  return is_open();
#  endif
}

template<access_mode AccessMode, typename ByteT>
void basic_mmap<AccessMode, ByteT>::swap(basic_mmap& other)
{
  if (this != &other) {
    using std::swap;
    swap(data_, other.data_);
    swap(file_handle_, other.file_handle_);
#  ifdef _WIN32
    swap(file_mapping_handle_, other.file_mapping_handle_);
#  endif
    swap(length_, other.length_);
    swap(mapped_length_, other.mapped_length_);
    swap(is_handle_internal_, other.is_handle_internal_);
  }
}

template<access_mode AccessMode, typename ByteT>
template<access_mode A>
typename std::enable_if<A == access_mode::write, void>::type
basic_mmap<AccessMode, ByteT>::conditional_sync()
{
  // This is invoked from the destructor, so not much we can do about
  // failures here.
  std::error_code ec;
  sync(ec);
}

template<access_mode AccessMode, typename ByteT>
template<access_mode A>
typename std::enable_if<A == access_mode::read, void>::type
basic_mmap<AccessMode, ByteT>::conditional_sync()
{
  // noop
}

template<access_mode AccessMode, typename ByteT>
bool operator==(const basic_mmap<AccessMode, ByteT>& a,
                const basic_mmap<AccessMode, ByteT>& b)
{
  return a.data() == b.data() && a.size() == b.size();
}

template<access_mode AccessMode, typename ByteT>
bool operator!=(const basic_mmap<AccessMode, ByteT>& a,
                const basic_mmap<AccessMode, ByteT>& b)
{
  return !(a == b);
}

template<access_mode AccessMode, typename ByteT>
bool operator<(const basic_mmap<AccessMode, ByteT>& a,
               const basic_mmap<AccessMode, ByteT>& b)
{
  if (a.data() == b.data()) {
    return a.size() < b.size();
  }
  return a.data() < b.data();
}

template<access_mode AccessMode, typename ByteT>
bool operator<=(const basic_mmap<AccessMode, ByteT>& a,
                const basic_mmap<AccessMode, ByteT>& b)
{
  return !(a > b);
}

template<access_mode AccessMode, typename ByteT>
bool operator>(const basic_mmap<AccessMode, ByteT>& a,
               const basic_mmap<AccessMode, ByteT>& b)
{
  if (a.data() == b.data()) {
    return a.size() > b.size();
  }
  return a.data() > b.data();
}

template<access_mode AccessMode, typename ByteT>
bool operator>=(const basic_mmap<AccessMode, ByteT>& a,
                const basic_mmap<AccessMode, ByteT>& b)
{
  return !(a < b);
}

}  // namespace mio

#endif  // MIO_BASIC_MMAP_IMPL

#endif  // MIO_MMAP_HEADER
/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef MIO_PAGE_HEADER
#define MIO_PAGE_HEADER

#ifdef _WIN32
#  include <windows.h>
#else
#  include <unistd.h>
#endif

namespace mio
{
/**
 * This is used by `basic_mmap` to determine whether to create a read-only or
 * a read-write memory mapping.
 */
enum class access_mode
{
  read,
  write
};

/**
 * Determines the operating system's page allocation granularity.
 *
 * On the first call to this function, it invokes the operating system specific
 * syscall to determine the page size, caches the value, and returns it. Any
 * subsequent call to this function serves the cached value, so no further
 * syscalls are made.
 */
inline size_t page_size()
{
  static const size_t page_size = []
  {
#ifdef _WIN32
    SYSTEM_INFO SystemInfo;
    GetSystemInfo(&SystemInfo);
    return SystemInfo.dwAllocationGranularity;
#else
    return sysconf(_SC_PAGE_SIZE);
#endif
  }();
  return page_size;
}

/**
 * Alligns `offset` to the operating's system page size such that it subtracts
 * the difference until the nearest page boundary before `offset`, or does
 * nothing if `offset` is already page aligned.
 */
inline size_t make_offset_page_aligned(size_t offset) noexcept
{
  const size_t page_size_ = page_size();
  // Use integer division to round down to the nearest page alignment.
  return offset / page_size_ * page_size_;
}

}  // namespace mio

#endif  // MIO_PAGE_HEADER
/* Copyright 2017 https://github.com/mandreyel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef MIO_SHARED_MMAP_HEADER
#define MIO_SHARED_MMAP_HEADER

// #include "mio/mmap.hpp"

#include <memory>  // std::shared_ptr
#include <system_error>  // std::error_code

namespace mio
{
/**
 * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with
 * `std::shared_ptr` semantics.
 *
 * This is not the default behaviour of `basic_mmap` to avoid allocating on the
 * heap if shared semantics are not required.
 */
template<access_mode AccessMode, typename ByteT>
class basic_shared_mmap
{
  using impl_type = basic_mmap<AccessMode, ByteT>;
  std::shared_ptr<impl_type> pimpl_;

public:
  using value_type = typename impl_type::value_type;
  using size_type = typename impl_type::size_type;
  using reference = typename impl_type::reference;
  using const_reference = typename impl_type::const_reference;
  using pointer = typename impl_type::pointer;
  using const_pointer = typename impl_type::const_pointer;
  using difference_type = typename impl_type::difference_type;
  using iterator = typename impl_type::iterator;
  using const_iterator = typename impl_type::const_iterator;
  using reverse_iterator = typename impl_type::reverse_iterator;
  using const_reverse_iterator = typename impl_type::const_reverse_iterator;
  using iterator_category = typename impl_type::iterator_category;
  using handle_type = typename impl_type::handle_type;
  using mmap_type = impl_type;

  basic_shared_mmap() = default;
  basic_shared_mmap(const basic_shared_mmap&) = default;
  basic_shared_mmap& operator=(const basic_shared_mmap&) = default;
  basic_shared_mmap(basic_shared_mmap&&) = default;
  basic_shared_mmap& operator=(basic_shared_mmap&&) = default;

  /** Takes ownership of an existing mmap object. */
  basic_shared_mmap(mmap_type&& mmap)
      : pimpl_(std::make_shared<mmap_type>(std::move(mmap)))
  {
  }

  /** Takes ownership of an existing mmap object. */
  basic_shared_mmap& operator=(mmap_type&& mmap)
  {
    pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
    return *this;
  }

  /** Initializes this object with an already established shared mmap. */
  basic_shared_mmap(std::shared_ptr<mmap_type> mmap)
      : pimpl_(std::move(mmap))
  {
  }

  /** Initializes this object with an already established shared mmap. */
  basic_shared_mmap& operator=(std::shared_ptr<mmap_type> mmap)
  {
    pimpl_ = std::move(mmap);
    return *this;
  }

#ifdef __cpp_exceptions
  /**
   * The same as invoking the `map` function, except any error that may occur
   * while establishing the mapping is wrapped in a `std::system_error` and is
   * thrown.
   */
  template<typename String>
  basic_shared_mmap(const String& path,
                    const size_type offset = 0,
                    const size_type length = map_entire_file)
  {
    std::error_code error;
    map(path, offset, length, error);
    if (error) {
      throw std::system_error(error);
    }
  }

  /**
   * The same as invoking the `map` function, except any error that may occur
   * while establishing the mapping is wrapped in a `std::system_error` and is
   * thrown.
   */
  basic_shared_mmap(const handle_type handle,
                    const size_type offset = 0,
                    const size_type length = map_entire_file)
  {
    std::error_code error;
    map(handle, offset, length, error);
    if (error) {
      throw std::system_error(error);
    }
  }
#endif  // __cpp_exceptions

  /**
   * If this is a read-write mapping and the last reference to the mapping,
   * the destructor invokes sync. Regardless of the access mode, unmap is
   * invoked as a final step.
   */
  ~basic_shared_mmap() = default;

  /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */
  std::shared_ptr<mmap_type> get_shared_ptr()
  {
    return pimpl_;
  }

  /**
   * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On
   * Windows, however, a mapped region of a file gets its own handle, which is
   * returned by 'mapping_handle'.
   */
  handle_type file_handle() const noexcept
  {
    return pimpl_ ? pimpl_->file_handle() : invalid_handle;
  }

  handle_type mapping_handle() const noexcept
  {
    return pimpl_ ? pimpl_->mapping_handle() : invalid_handle;
  }

  /** Returns whether a valid memory mapping has been created. */
  bool is_open() const noexcept
  {
    return pimpl_ && pimpl_->is_open();
  }

  /**
   * Returns true if no mapping was established, that is, conceptually the
   * same as though the length that was mapped was 0. This function is
   * provided so that this class has Container semantics.
   */
  bool empty() const noexcept
  {
    return !pimpl_ || pimpl_->empty();
  }

  /**
   * `size` and `length` both return the logical length, i.e. the number of
   * bytes user requested to be mapped, while `mapped_length` returns the actual
   * number of bytes that were mapped which is a multiple of the underlying
   * operating system's page allocation granularity.
   */
  size_type size() const noexcept
  {
    return pimpl_ ? pimpl_->length() : 0;
  }
  size_type length() const noexcept
  {
    return pimpl_ ? pimpl_->length() : 0;
  }
  size_type mapped_length() const noexcept
  {
    return pimpl_ ? pimpl_->mapped_length() : 0;
  }

  /**
   * Returns a pointer to the first requested byte, or `nullptr` if no memory
   * mapping exists.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  pointer data() noexcept
  {
    return pimpl_->data();
  }
  const_pointer data() const noexcept
  {
    return pimpl_ ? pimpl_->data() : nullptr;
  }

  /**
   * Returns an iterator to the first requested byte, if a valid memory mapping
   * exists, otherwise this function call is undefined behaviour.
   */
  iterator begin() noexcept
  {
    return pimpl_->begin();
  }
  const_iterator begin() const noexcept
  {
    return pimpl_->begin();
  }
  const_iterator cbegin() const noexcept
  {
    return pimpl_->cbegin();
  }

  /**
   * Returns an iterator one past the last requested byte, if a valid memory
   * mapping exists, otherwise this function call is undefined behaviour.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  iterator end() noexcept
  {
    return pimpl_->end();
  }
  const_iterator end() const noexcept
  {
    return pimpl_->end();
  }
  const_iterator cend() const noexcept
  {
    return pimpl_->cend();
  }

  /**
   * Returns a reverse iterator to the last memory mapped byte, if a valid
   * memory mapping exists, otherwise this function call is undefined
   * behaviour.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  reverse_iterator rbegin() noexcept
  {
    return pimpl_->rbegin();
  }
  const_reverse_iterator rbegin() const noexcept
  {
    return pimpl_->rbegin();
  }
  const_reverse_iterator crbegin() const noexcept
  {
    return pimpl_->crbegin();
  }

  /**
   * Returns a reverse iterator past the first mapped byte, if a valid memory
   * mapping exists, otherwise this function call is undefined behaviour.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  reverse_iterator rend() noexcept
  {
    return pimpl_->rend();
  }
  const_reverse_iterator rend() const noexcept
  {
    return pimpl_->rend();
  }
  const_reverse_iterator crend() const noexcept
  {
    return pimpl_->crend();
  }

  /**
   * Returns a reference to the `i`th byte from the first requested byte (as
   * returned by `data`). If this is invoked when no valid memory mapping has
   * been created prior to this call, undefined behaviour ensues.
   */
  reference operator[](const size_type i) noexcept
  {
    return (*pimpl_)[i];
  }
  const_reference operator[](const size_type i) const noexcept
  {
    return (*pimpl_)[i];
  }

  /**
   * Establishes a memory mapping with AccessMode. If the mapping is
   * unsuccesful, the reason is reported via `error` and the object remains in a
   * state as if this function hadn't been called.
   *
   * `path`, which must be a path to an existing file, is used to retrieve a
   * file handle (which is closed when the object destructs or `unmap` is
   * called), which is then used to memory map the requested region. Upon
   * failure, `error` is set to indicate the reason and the object remains in an
   * unmapped state.
   *
   * `offset` is the number of bytes, relative to the start of the file, where
   * the mapping should begin. When specifying it, there is no need to worry
   * about providing a value that is aligned with the operating system's page
   * allocation granularity. This is adjusted by the implementation such that
   * the first requested byte (as returned by `data` or `begin`), so long as
   * `offset` is valid, will be at `offset` from the start of the file.
   *
   * `length` is the number of bytes to map. It may be `map_entire_file`, in
   * which case a mapping of the entire file is created.
   */
  template<typename String>
  void map(const String& path,
           const size_type offset,
           const size_type length,
           std::error_code& error)
  {
    map_impl(path, offset, length, error);
  }

  /**
   * Establishes a memory mapping with AccessMode. If the mapping is
   * unsuccesful, the reason is reported via `error` and the object remains in a
   * state as if this function hadn't been called.
   *
   * `path`, which must be a path to an existing file, is used to retrieve a
   * file handle (which is closed when the object destructs or `unmap` is
   * called), which is then used to memory map the requested region. Upon
   * failure, `error` is set to indicate the reason and the object remains in an
   * unmapped state.
   *
   * The entire file is mapped.
   */
  template<typename String>
  void map(const String& path, std::error_code& error)
  {
    map_impl(path, 0, map_entire_file, error);
  }

  /**
   * Establishes a memory mapping with AccessMode. If the mapping is
   * unsuccesful, the reason is reported via `error` and the object remains in a
   * state as if this function hadn't been called.
   *
   * `handle`, which must be a valid file handle, which is used to memory map
   * the requested region. Upon failure, `error` is set to indicate the reason
   * and the object remains in an unmapped state.
   *
   * `offset` is the number of bytes, relative to the start of the file, where
   * the mapping should begin. When specifying it, there is no need to worry
   * about providing a value that is aligned with the operating system's page
   * allocation granularity. This is adjusted by the implementation such that
   * the first requested byte (as returned by `data` or `begin`), so long as
   * `offset` is valid, will be at `offset` from the start of the file.
   *
   * `length` is the number of bytes to map. It may be `map_entire_file`, in
   * which case a mapping of the entire file is created.
   */
  void map(const handle_type handle,
           const size_type offset,
           const size_type length,
           std::error_code& error)
  {
    map_impl(handle, offset, length, error);
  }

  /**
   * Establishes a memory mapping with AccessMode. If the mapping is
   * unsuccesful, the reason is reported via `error` and the object remains in a
   * state as if this function hadn't been called.
   *
   * `handle`, which must be a valid file handle, which is used to memory map
   * the requested region. Upon failure, `error` is set to indicate the reason
   * and the object remains in an unmapped state.
   *
   * The entire file is mapped.
   */
  void map(const handle_type handle, std::error_code& error)
  {
    map_impl(handle, 0, map_entire_file, error);
  }

  /**
   * If a valid memory mapping has been created prior to this call, this call
   * instructs the kernel to unmap the memory region and disassociate this
   * object from the file.
   *
   * The file handle associated with the file that is mapped is only closed if
   * the mapping was created using a file path. If, on the other hand, an
   * existing file handle was used to create the mapping, the file handle is not
   * closed.
   */
  void unmap()
  {
    if (pimpl_)
      pimpl_->unmap();
  }

  void swap(basic_shared_mmap& other)
  {
    pimpl_.swap(other.pimpl_);
  }

  /** Flushes the memory mapped page to disk. Errors are reported via `error`.
   */
  template<access_mode A = AccessMode,
           typename = typename std::enable_if<A == access_mode::write>::type>
  void sync(std::error_code& error)
  {
    if (pimpl_)
      pimpl_->sync(error);
  }

  /** All operators compare the underlying `basic_mmap`'s addresses. */

  friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b)
  {
    return a.pimpl_ == b.pimpl_;
  }

  friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b)
  {
    return !(a == b);
  }

  friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b)
  {
    return a.pimpl_ < b.pimpl_;
  }

  friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b)
  {
    return a.pimpl_ <= b.pimpl_;
  }

  friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b)
  {
    return a.pimpl_ > b.pimpl_;
  }

  friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b)
  {
    return a.pimpl_ >= b.pimpl_;
  }

private:
  template<typename MappingToken>
  void map_impl(const MappingToken& token,
                const size_type offset,
                const size_type length,
                std::error_code& error)
  {
    if (!pimpl_) {
      mmap_type mmap = make_mmap<mmap_type>(token, offset, length, error);
      if (error) {
        return;
      }
      pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
    } else {
      pimpl_->map(token, offset, length, error);
    }
  }
};

/**
 * This is the basis for all read-only mmap objects and should be preferred over
 * directly using basic_shared_mmap.
 */
template<typename ByteT>
using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>;

/**
 * This is the basis for all read-write mmap objects and should be preferred
 * over directly using basic_shared_mmap.
 */
template<typename ByteT>
using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>;

/**
 * These aliases cover the most common use cases, both representing a raw byte
 * stream (either with a char or an unsigned char/uint8_t).
 */
using shared_mmap_source = basic_shared_mmap_source<char>;
using shared_ummap_source = basic_shared_mmap_source<unsigned char>;

using shared_mmap_sink = basic_shared_mmap_sink<char>;
using shared_ummap_sink = basic_shared_mmap_sink<unsigned char>;

}  // namespace mio

#endif  // MIO_SHARED_MMAP_HEADER
